package com.hzgj.soa.spring;

import com.google.common.collect.Maps;

import com.hzgj.bcl.soa.config.ServerDef;
import com.hzgj.bcl.soa.config.ServiceDef;
import com.hzgj.bcl.soa.util.NetUtils;
import com.hzgj.bcl.util.lang.StrKit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import static com.hzgj.bcl.soa.util.Constants.DEFAULT_PROTOCOL;
import static com.hzgj.bcl.soa.util.Constants.DEFAULT_REGISTRY;
import static com.hzgj.soa.spring.schema.SoaNamespaceHandler.DOMAINCONFIGBEAN_NAME;

/**
 * Created by ye.pan on 2015/7/10.
 */
public class ServerConfig {

    public static final int defaultPort = 19089;
    private static final Map<String, Integer> RANDOM_PORT_MAP = new HashMap<>();
    private Logger logger = LoggerFactory.getLogger(getClass());

    private static Integer getRandomPort(String protocol) {
        protocol = protocol.toLowerCase();
        if (RANDOM_PORT_MAP.containsKey(protocol)) {
            return RANDOM_PORT_MAP.get(protocol);
        }
        return Integer.MIN_VALUE;
    }

    private static void putRandomPort(String protocol, Integer port) {
        protocol = protocol.toLowerCase();
        if (!RANDOM_PORT_MAP.containsKey(protocol)) {
            RANDOM_PORT_MAP.put(protocol, port);
        }
    }

    public void afterPropertiesSet(ApplicationContext applicationContext) {
        Map<String, ServerDef> serverConfigs = applicationContext.getBeansOfType(ServerDef.class);
        Map<String, ServiceDef.Attr> serviceConfigs = applicationContext.getBeansOfType(ServiceDef.Attr.class);
        //serverId:protocol
        Map<String, String> server2Protocol = Maps.newHashMap();
        for (ServiceDef.Attr attr : serviceConfigs.values()) {
            processServiceBefore(attr, applicationContext);
            if (StrKit.isBlank(attr.getServerDef().getId())) {
                throw new RuntimeException("service's server must be set,because of finding server element more than one");
            }
            ServerDef serverDef = serverConfigs.get(attr.getServerDef().getId());
            if (serverDef == null) {
                throw new RuntimeException("can't get server by " + attr.getServerDef().getId());
            }
            String _protocol = server2Protocol.get(attr.getServer());
            if (StrKit.isBlank(_protocol)) {
                server2Protocol.put(attr.getServer(), attr.getProtocol());
            } else {
                if (!_protocol.equals(attr.getProtocol())) {
                    throw new RuntimeException("server " + attr.getServer() + " only service for same protocol");
                }
            }
            buildServer(serverDef, attr.getProtocol());
            attr.setTime(new Date());
            attr.setServerDef(serverDef);
            serverDef.getServiceDefs().add(attr);
        }
    }

    private void processServiceBefore(ServiceDef.Attr attr, ApplicationContext applicationContext) {

        ServiceConfig serviceConfig = null;
        String domain = attr.getDomain();

        ApplicationConfig applicationConfig = applicationContext.getBean(ApplicationConfig.class);
        if (applicationConfig == null) {
            throw new RuntimeException("application musst be set.");
        }
        attr.setApplication(applicationConfig.name);

        DomainConfigBean domainConfigBean = applicationContext.getBean(DOMAINCONFIGBEAN_NAME, DomainConfigBean.class);

        if (StrKit.notBlank(domain)) {
            Domain domainConfig = domainConfigBean.getDomain(domain);
            serviceConfig = domainConfig.serviceConfig;
        }

        if (serviceConfig == null) {
            serviceConfig = domainConfigBean.getGlobalServiceConfig();
            if (serviceConfig == null) {
                return;
            }
        }

        if (StrKit.isBlank(attr.getAuthor())) {
            attr.setAuthor(serviceConfig.getAuthor());
        }
        if (StrKit.isBlank(attr.getGroup())) {
            attr.setGroup(serviceConfig.getGroup());
        }
        if (StrKit.isBlank(attr.getVersion())) {
            attr.setVersion(serviceConfig.getVersion());
        }
        if (attr.getProtocol().equals(DEFAULT_PROTOCOL) && !serviceConfig.getProtocol().equals(DEFAULT_PROTOCOL)) {
            attr.setProtocol(serviceConfig.getProtocol());
        }
        if (attr.isRegister() == DEFAULT_REGISTRY && serviceConfig.isRegister() != DEFAULT_REGISTRY) {
            attr.setRegister(serviceConfig.isRegister());
        }
        if (StrKit.isBlank(attr.getServer())) {
            if (StrKit.isBlank(serviceConfig.getServer())) {
                throw new RuntimeException("service " + attr.getInterface() + " need attr of server");
            }
            attr.setServerDef(serviceConfig.getServerDef());
        }
    }

    private ServerDef buildServer(ServerDef server, String protocol) {
        server.setHost(NetUtils.getAndCheckHost(server.getHost()));
        server.setPort(getPort(server.getPort(), defaultPort, protocol));
        server.setProtocol(protocol);
        return server;
    }

    private Integer getPort(Integer port, Integer dPort, String protocol) {
        if (port == null || port < 0) {
            port = dPort;
        } else if (port <= 0) {
            port = getRandomPort(protocol);
            if (port == null || port < 0) {
                port = NetUtils.getAvailablePort(defaultPort);
                putRandomPort(protocol, port);
            }
            logger.warn("Use random available port(" + port + ") for protocol "
                    + protocol);
        }
        return port;
    }
}
